import "gl-matrix";

import { mat4 } from "gl-matrix";
import {
  cubeData,
  getFloat32Array,
  getInt16Array,
  initTexture,
  planeData,
} from "./helper";

let gl: WebGLRenderingContext = null;
let canvas: HTMLCanvasElement = null;
let texture: WebGLTexture = null;
var program: WebGLProgram;
var vertexShader: WebGLShader;
var fragmentShader: WebGLShader;
let projection: mat4 = null; //投影矩阵
let plane: Plane = null;
let plane1: Plane1 = null;
const vertexShaderCode =
  "precision mediump float;" +
  "attribute vec3 position;" +
  "attribute vec4 inColor;" +
  "attribute vec2 inUV;" +
  "uniform mat4 projection;" +
  "uniform float vertexAlpha;" +
  "varying vec4 outColor;" +
  "varying vec2 outUV;" +
  "void main() {" +
  "gl_Position = projection * vec4(position,1.0);" +
  "outColor = vec4(inColor.rgb,inColor.a*vertexAlpha);" +
  "outColor=inColor;" +
  "outUV = inUV;" +
  "}";
const fragmentShaderCode =
  "precision mediump float;" +
  "uniform int useTexture;" +
  "uniform sampler2D texture;" +
  "varying vec4 outColor;" +
  "varying vec2 outUV;" +
  "void main() {" +
  "vec4 destColor = vec4(0.0);" +
  "if(bool(useTexture)){" +
  "vec4 smpColor=texture2D(texture,outUV);" +
  "destColor = smpColor*outColor;}else{" +
  "destColor = outColor;" +
  "}" +
  "gl_FragColor = destColor;" +
  // "gl_FragColor=outColor;" +
  "}";
// 准备WebGL的绘制上下文
function prepare() {
  canvas = document.createElement("canvas");
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight - 20;
  canvas.style.width = "" + canvas.width;
  canvas.style.height = "" + canvas.height;
  canvas.style.overflow = "hidden";
  document.body.append(canvas);
  gl = canvas.getContext("webgl");
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
  gl.enable(gl.BLEND);
  gl.disable(gl.DEPTH_TEST);
}

// 准备WebGL需要使用的素材
function webglPrepare() {
  prepareVertexData();
  createProgram();
}

var vertexData: WebGLBuffer;
function loadResource() {
  const p1 = initTexture(gl, "./wall1.png");
  return new Promise((reslove) => {
    Promise.all([p1]).then((result) => {
      texture = result[0];
      reslove(result);
    });
  });
}
function prepareVertexData() {
  // Prepare Vertex Data

  let vertices: Float32Array = getFloat32Array(cubeData);

  // 在GPU上为顶点数据开辟空间
  vertexData = gl.createBuffer();
  // 将上面开辟的空间进入编辑模式
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexData);
  // 向上面开辟的空间写入数据
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
}

function createProgram() {
  program = gl.createProgram();

  vertexShader = compileShader(vertexShaderCode, gl.VERTEX_SHADER);
  fragmentShader = compileShader(fragmentShaderCode, gl.FRAGMENT_SHADER);
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);

  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    console.log(
      "Unable to initialize the shader program: " +
        gl.getProgramInfoLog(program)
    );
  }
  return program;
}

function compileShader(shaderSrc: string, shaderType: number): WebGLShader {
  let shader = gl.createShader(shaderType);
  gl.shaderSource(shader, shaderSrc);
  gl.compileShader(shader);
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    console.log(
      "An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader)
    );
    gl.deleteShader(shader);
    return null;
  }
  return shader;
}
function degToRadian(deg: number): number {
  return (deg * Math.PI) / 180;
}

function render() {
  gl.viewport(0, 0, canvas.width, canvas.height);
  projection = mat4.create();
  mat4.perspective(
    projection,
    (60 * Math.PI) / 180,
    canvas.width / canvas.height,
    0.01,
    1000
  );
  // mat4.ortho(projection, 0, canvas.width, canvas.height, 0, 0.01, 10000);
  gl.clearColor(0, 0, 0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  gl.clearDepth(1.0);
  gl.enable(gl.DEPTH_TEST);
  gl.useProgram(program);
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexData);
  if (!plane) {
    plane = new Plane();
  }
  plane.draw();

  if (!plane1) {
    plane1 = new Plane1();
  }
  plane1.draw();

  // renderCube()

  requestAnimationFrame(render);
}
abstract class Entity {
  //矩阵
  matTrans = mat4.create();
  matScale = mat4.create();
  matModel = mat4.create();
  matRotate = mat4.create();
  matAll = mat4.create();
  mvp = mat4.create();
  //uniform
  uniformLoc = gl.getUniformLocation(program, "projection");
  uniformAlphaLoc = gl.getUniformLocation(program, "vertexAlpha");
  uniformTextureLoc = gl.getUniformLocation(program, "texture");
  uniformUseTextureLoc = gl.getUniformLocation(program, "useTexture");
  //attr
  positionLoc = gl.getAttribLocation(program, "position");
  colorLoc = gl.getAttribLocation(program, "inColor");
  uvLoc = gl.getAttribLocation(program, "inUV");
  //buffer
  dataBuffer = gl.createBuffer();
  colorBuffer = gl.createBuffer();
  textureBuffer = gl.createBuffer();
  indexBuffer = gl.createBuffer();
  //data
  planeData = planeData;
  abstract transform(): void;
  abstract set(): void;
  constructor() {}
  identity() {
    mat4.identity(this.matTrans);
    mat4.identity(this.matRotate);
    mat4.identity(this.matScale);
    mat4.identity(this.matModel);
    mat4.identity(this.matAll);
    mat4.identity(this.mvp);
  }
  loadVertice() {
    const data = getFloat32Array([this.planeData.position]);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.dataBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
    gl.enableVertexAttribArray(this.positionLoc);
    gl.vertexAttribPointer(this.positionLoc, 3, gl.FLOAT, false, 0, 0);
  }
  loadColor() {
    const data = getFloat32Array([this.planeData.color]);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
    gl.enableVertexAttribArray(this.colorLoc);
    gl.vertexAttribPointer(this.colorLoc, 4, gl.FLOAT, false, 0, 0);
  }
  loadTexture() {
    const data = getFloat32Array([this.planeData.textureCoord]);
    gl.bindBuffer(gl.ARRAY_BUFFER, this.textureBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
    gl.enableVertexAttribArray(this.uvLoc);
    gl.vertexAttribPointer(this.uvLoc, 2, gl.FLOAT, false, 0, 0);
  }
  loadIndex() {
    const data = getInt16Array([this.planeData.index]);
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, gl.STATIC_DRAW);
  }
  renderTexture() {
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.uniform1i(this.uniformTextureLoc, 0);
    gl.bindTexture(gl.TEXTURE_2D, null);
  }
  draw(): mat4 {
    this.identity();
    this.loadVertice(); //加载顶点
    this.loadColor();
    this.loadTexture();
    this.loadIndex();
    this.renderTexture();
    this.transform();
    this.set();
    gl.uniformMatrix4fv(this.uniformLoc, false, this.mvp);
    gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
    return this.getModel();
  }
  getModel() {
    return this.matAll;
  }
}
class Plane extends Entity {
  rot = 0;
  transform(): void {
    mat4.translate(this.matTrans, this.matTrans, [0.25, 0.25, -0.25]);
    mat4.rotate(
      this.matRotate,
      this.matRotate,
      degToRadian(this.rot++),
      [0, 1, 0]
    );
    // mat4.multiply(this.matAll, this.matTrans, this.matRotate);
    // mat4.multiply(this.mvp, projection, this.matAll);
    mat4.multiply(this.matModel, this.matTrans, this.matRotate);

    mat4.multiply(this.mvp, this.matAll, this.matModel);
  }
  set(): void {
    gl.uniform1f(this.uniformAlphaLoc, 0.5);
    gl.uniform1i(this.uniformUseTextureLoc, 0);
  }
}
class Plane1 extends Entity {
  transform(): void {
    mat4.translate(this.matTrans, this.matTrans, [0, 0.25, -0.5]);
    mat4.rotate(this.matRotate, this.matRotate, degToRadian(0), [0, 1, 0]);
    mat4.multiply(this.matModel, this.matRotate, this.matTrans);
    mat4.multiply(this.mvp, this.matAll, this.matModel);
  }
  set(): void {
    gl.uniform1f(this.uniformAlphaLoc, 1);
    gl.uniform1i(this.uniformUseTextureLoc, 1);
  }
}
window.onload = () => {
  prepare();
  webglPrepare();
  loadResource().then(() => {
    render();
  });
};
